package com.example.demoappsocket;

import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;

import androidx.annotation.NonNull;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.NoRouteToHostException;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;

/**
 * @author Elliot
 */
@SuppressWarnings("AlibabaSwitchStatement")
public class SocketClient {
    private static String SOCKET_CLIENT = "socket_client";
    private static SocketClient sSocketClient;
    private Socket mSocket;
    private ConnectListener mListener;
    private Thread mConnectThread;

    private String mServerIp = null;
    private int mServerPort = 0;

    private Boolean mIsReConnect = true;

    public final static int MSG_SOKECT_CONNECT_SUCCESS = 1000;
    public final static int MSG_SOKECT_EXCEPTION = 1001;
    public final static int MSG_RECEVICE_DATA = 1002;
    public final static int MSG_INPUT_SHUT_DOWN = 1003;
    public final static int MSG_OUTPUT_SHUT_DOWN = 1004;
    public final static int MSG_CONNECT_ERROR = 1005;


    private final String MSG_DATA_TYPE_DATA = "data";
    private final String MSG_DATA_TYPE_MSG = "message";

    public static SocketClient getInstance() {
        if (sSocketClient == null) {
            sSocketClient = new SocketClient();
        }
        return sSocketClient;
    }


    Handler mHandler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case MSG_RECEVICE_DATA:
                    if (mListener != null) {
                        mListener.onReceiveData(msg.getData().getString(MSG_DATA_TYPE_DATA));
                    }
                    break;
                case MSG_SOKECT_CONNECT_SUCCESS:
                    if (mListener != null) {
                        mListener.onConnectionSucceeded("连接成功");
                    }
                    break;
                case MSG_SOKECT_EXCEPTION:
                    if (mListener != null) {
                        mListener.onConnectionException(msg.getData().getString(MSG_DATA_TYPE_MSG));
                    }
                    break;
                case MSG_INPUT_SHUT_DOWN:
                    Log.d(SOCKET_CLIENT, msg.getData().getString(MSG_DATA_TYPE_MSG));
                    if (mListener != null) {
                        mListener.onInputShutDown(msg.getData().getString(MSG_DATA_TYPE_MSG));
                    }
                    break;
                case MSG_OUTPUT_SHUT_DOWN:
                    Log.d(SOCKET_CLIENT, msg.getData().getString(MSG_DATA_TYPE_MSG));
                    if (mListener != null) {
                        mListener.onOutputShutDown(msg.getData().getString(MSG_DATA_TYPE_MSG));
                    }
                    break;
                case MSG_CONNECT_ERROR:
                    if (mListener != null) {
                        mListener.onConnectError(msg.getData().getString(MSG_DATA_TYPE_MSG));
                    }
                default:
                    break;
            }
        }
    };

    /**
     * 创建连接
     *
     * @param serIp
     * @param serPort
     */
    public void initConnect(final String serIp, final int serPort) {
        mIsReConnect = true;
        if (mSocket == null && mConnectThread == null) {
            this.mServerIp = serIp;
            this.mServerPort = serPort;
            //noinspection AlibabaAvoidManuallyCreateThread
            if (mConnectThread == null) {
                mConnectThread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        connect(serIp, serPort);
                    }
                });
            }
            mConnectThread.start();
        }

    }

    /**
     * 与服务端进行连接
     */
    private void connect(String mSerIp, int mSerPort) {
        int connectCount = 0;
        //检测是否实例化，如果没有实例化则进行实例化
        while (mSocket == null && mIsReConnect) {
            connectCount++;
            if (mListener != null) {
                mListener.onConnectionLoading(connectCount);
            }
            if (mSocket == null) {
                mSocket = new Socket();
            }
            try {
//                mSocket = new Socket(mSerIP, mSerPort);
                mSocket.connect(new InetSocketAddress(mSerIp, mSerPort), 2000);
                if (mSocket.isConnected()) {
                    handleConnectSuccess();
                }
            } catch (IOException e) {
                e.printStackTrace();
                if (e instanceof SocketException || e instanceof ConnectException) {
                    Log.e(SOCKET_CLIENT, "连接异常，请重连：" + e.getCause() + " " + e.getMessage());
                    handleConnectException("连接异常，请重连：" + e.getCause() + " " + e.getMessage());
                    releaseSocket();// 连接异常，释放资源
                } else if (e instanceof SocketTimeoutException) {
                    Log.e(SOCKET_CLIENT, "连接超时，正在重连");
                    releaseReconnect(); // 连接超时，正在重连
                } else if (e instanceof NoRouteToHostException) {
                    Log.e(SOCKET_CLIENT, "该地址不存在，请检查");
                    mConnectThread.interrupt();
                } else if (e instanceof ConnectException) {
                    Log.e(SOCKET_CLIENT, "连接异常或被拒绝，请检查");
                    mConnectThread.interrupt();
                }
                //连接3次
                if (connectCount == 3) {
                    Log.e(SOCKET_CLIENT, e.getCause() + e.getMessage());
                    handleConnectError(e.getCause() + e.getMessage());
                    mIsReConnect = false;
                    releaseSocket();
                    connectCount = 0;
                    return;
                }
            }
        }
        receiveData();
    }


    /**
     * 释放资源，重新连接
     */
    private void releaseReconnect() {
        releaseSocket();
        reconnect();
    }


    /**
     * 接收数据
     */
    private void receiveData() {
        if (mSocket != null) {
            try {
                //接收数据
                if (!mSocket.isInputShutdown()) {
                    InputStream inputStream = mSocket.getInputStream();
                    byte[] buffer = new byte[1024];
                    int len = -1;
                    //读取数据
                    while ((len = inputStream.read(buffer)) != -1) {
                        String data = new String(buffer, 0, len);
                        handleReceiveData(data);
                    }
                } else {
                    handleInputShutdown();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }


    /**
     * 发送数据
     *
     * @param data
     */
    public void sendData(final String data) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                if (mSocket != null) {
                    try {
                        if (!mSocket.isOutputShutdown()) {
                            OutputStream outputStream = mSocket.getOutputStream();
                            outputStream.write(data.getBytes());
                        } else {

                            handleOutputShutdown();
                        }

                    } catch (Exception e) {
                        // 如果socket的输出流关闭
                        if (mListener != null) {
                            mListener.onBrokenPipe(e.getMessage());
                        }
                        releaseReconnect(); // 输出流断开，重新连接
                    }
                }
            }
        }).start();
    }


    /**
     * 断开连接
     *
     * @throws IOException
     */
    public void disconnect() {
        if (mSocket != null) {
            try {
                mSocket.shutdownInput();
                mSocket.shutdownOutput();
                mSocket.close();
                mSocket = null;
                mConnectThread = null;
                mListener.onDisconnect();
            } catch (IOException e) {
                Log.d("断开连接", e.getMessage());
            }
        }
    }

    /**
     * 释放资源
     */
    private void releaseSocket() {
        if (mSocket != null) {
            try {
                mSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            mSocket = null;
        }

        if (mConnectThread != null) {
            mConnectThread = null;
        }


    }

    private void reconnect() {
        //重新初始化socket
        if (mServerIp != null && mServerPort != 0 && mIsReConnect) {
            Log.d("SoketClient", "重新连接");
            initConnect(mServerIp, mServerPort);
        }
    }

    /**
     * 发送 Socket 连接成功
     */
    private void handleConnectSuccess() {
        Message message = new Message();
        message.what = MSG_SOKECT_CONNECT_SUCCESS;
        mHandler.sendMessage(message);
    }

    private void handleConnectException(String msg) {
        Message message = new Message();
        message.what = MSG_SOKECT_EXCEPTION;
        Bundle bundle = new Bundle();
        bundle.putString(MSG_DATA_TYPE_MSG, msg);
        message.setData(bundle);
        mHandler.sendMessage(message);
    }

    /**
     * 发送接收数据消息
     *
     * @param data
     */
    private void handleReceiveData(String data) {
        Message message = new Message();
        message.what = MSG_RECEVICE_DATA;
        Bundle bundle = new Bundle();
        bundle.putString(MSG_DATA_TYPE_DATA, data);
        message.setData(bundle);
        mHandler.sendMessage(message);
    }

    private void handleInputShutdown() {
        Message message = new Message();
        message.what = MSG_INPUT_SHUT_DOWN;
        Bundle bundle = new Bundle();
        bundle.putString(MSG_DATA_TYPE_MSG, "输入流关闭");
        message.setData(bundle);
        mHandler.sendMessage(message);
    }

    private void handleOutputShutdown() {
        Message message = new Message();
        message.what = MSG_OUTPUT_SHUT_DOWN;
        Bundle bundle = new Bundle();
        bundle.putString(MSG_DATA_TYPE_MSG, "输出流关闭");
        message.setData(bundle);
        mHandler.sendMessage(message);
    }

    private void handleConnectError(String msg) {
        Message message = new Message();
        message.what = MSG_CONNECT_ERROR;
        Bundle bundle = new Bundle();
        bundle.putString(MSG_DATA_TYPE_MSG, msg);
        message.setData(bundle);
        mHandler.sendMessage(message);
    }


    public void setOnConnectListener(ConnectListener listener) {
        this.mListener = listener;
    }

    /**
     * 对外暴露监听回调
     */
    public interface ConnectListener {

        /**
         * 连接成功
         *
         * @param msg 成功信息
         */
        void onConnectionSucceeded(String msg);

        /**
         * 连接成功
         *
         * @param msg 异常信息
         */
        void onConnectionException(String msg);

        /**
         * 连接等待
         *
         * @param i 值为连接次数
         */
        void onConnectionLoading(int i);

        /**
         * 接收数据
         *
         * @param data 值为接收到的数据
         */
        void onReceiveData(String data);

        /**
         * 连接错误，目前用于未连接到网络、连接超时...
         *
         * @param error 错误信息
         */
        void onConnectError(String error);

        /**
         * 连接中断(连接断开后无法发送数据)
         *
         * @param msg 连接中断信息
         */
        void onBrokenPipe(String msg);

        /**
         * 断开
         */
        void onDisconnect();

        /**
         * 输入流关闭
         *
         * @param msg
         */
        void onInputShutDown(String msg);

        /**
         * 输出流关闭
         *
         * @param msg
         */
        void onOutputShutDown(String msg);
    }
}
